package lrscp.lib.tcp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import lrscp.lib.Common;
import lrscp.lib.Log;

/**
 * using two socket ports to communicate to each other
 * 
 * @author 80055901
 * 
 */
public class TcpStringCommunicationOld {
    private static final int SERVER_PORT = 5000;
    private static final int CLIENT_PORT = 6000;
    private static final boolean DEBUG = false;
    private static final String tag = "TcpStringCommunication";

    // client
    private String mAddr = null;
    private int mClientPort = -1;
    private Socket mClientSocket = null;

    // server
    private int mServerPort = -1;
    private Socket mServerSocket = null;

    private String mRecvKey = null;
    private String mRecvContent = null;
    private String mSendKey = null;
    private String mSendContent = null;

    private Object mRecvLock = new Object();
    private Object mSendLock = new Object();
    private boolean exit = false;

    public TcpStringCommunicationOld(String address, int serverPort, int clientPort) {
        mAddr = address;
        mServerPort = serverPort;
        mClientPort = clientPort;
        mClientThd.start();
        mServerThd.start();
    }

    /**
     * 
     * @param address
     *            the address of the computer you want to talk to.
     * @param isServer
     *            you shoud assign a server side and a client side before
     *            communication.
     */
    public TcpStringCommunicationOld(String address, boolean isServer) {
        mAddr = address;
        if (isServer) {
            mServerPort = SERVER_PORT;
            mClientPort = CLIENT_PORT;
        } else {
            mServerPort = CLIENT_PORT;
            mClientPort = SERVER_PORT;
        }
        mClientThd.start();
        mServerThd.start();
        Log.i(tag, "mAddr:" + address + " mServerPort:" + mServerPort + " mClientPort:"
                + mClientPort);
    }

    /**
     * Listening on a socket port to retrive a given key's content. Will block
     * until received.
     * 
     * @param key
     * @return
     * @throws InterruptedException
     */
    public String getContent(String key) throws InterruptedException {
        String content = null;
        synchronized (mRecvLock) {
            mRecvKey = key;
            while (mRecvContent == null && !exit) {
                mRecvLock.wait();
            }
            content = mRecvContent;
            mRecvKey = null;
            mRecvContent = null;
        }
        return content;
    }

    public void sendContent(String key, String content) throws InterruptedException {
        synchronized (mSendLock) {
            mSendKey = key;
            mSendContent = content;
            mSendLock.notifyAll();
            while (mSendKey != null && !exit)
                mSendLock.wait();
        }
    }

    public void dispose() {
        exit = true;
        try {
            mServerSocket.close();
            mClientSocket.close();
            synchronized (mSendLock) {
                mSendLock.notifyAll();
            }
            synchronized (mRecvLock) {
                mRecvLock.notifyAll();
            }
            mClientThd.join();
            mServerThd.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (DEBUG)
            Log.i(tag, "dispose sucess");
    }

    private Thread mClientThd = new Thread(new Runnable() {
        @Override
        public void run() {
            while (!exit) {
                try {
                    mClientSocket = new Socket(mAddr, mClientPort);
                    while (!mClientSocket.isConnected() && !exit) {
                        Common.sleep(300);
                    }
                    if (exit)
                        return;
                    Log.i("client", "Connected to server");
                    mClientSocket.setKeepAlive(true);
                    handleClientStream(mClientSocket.getInputStream(),
                            mClientSocket.getOutputStream());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (mClientSocket != null)
                            mClientSocket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                Common.sleep(500);
            }
            Log.i("client thread exit!");
        }
    });

    private void handleClientStream(InputStream is, OutputStream os) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        boolean isOk = false;
        String line = null;

        while ((line = br.readLine()) != null) {
            if (!isOk) {
                if (confirm(br, bw, line)) {
                    isOk = true;
                    Log.i("client", "isok");
                }
            } else if (isOk && confirm(br, bw, line)) {
                int idx = line.indexOf(':');
                String key = null;
                String content = null;
                if (idx > 0) {
                    key = line.substring(0, idx);
                    content = line.substring(idx + 1);
                    synchronized (mRecvLock) {
                        if (key.equals(mRecvKey)) {
                            mRecvContent = content;
                            mRecvLock.notifyAll();
                        }
                    }
                }
            }
        }
    }

    private Thread mServerThd = new Thread(new Runnable() {
        @Override
        public void run() {
            while (!exit) {
                ServerSocket server = null;
                try {
                    server = new ServerSocket(mServerPort);
                    mServerSocket = server.accept();
                    Log.i("server", "Get a client");
                    mServerSocket.setKeepAlive(true);
                    handleServerStream(mServerSocket.getInputStream(),
                            mServerSocket.getOutputStream());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (mServerSocket != null)
                            mServerSocket.close();
                        if (server != null)
                            server.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                Common.sleep(500);
            }
            Log.i("server", "server thread exit!");
        }
    });

    private void handleServerStream(InputStream is, OutputStream os) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
        boolean isOk = false;
        String line = null;

        while (!exit) {
            if (!isOk) {
                send(br, bw, "are you ok");
                isOk = true;
                Log.i("server", "isok");
            } else {
                synchronized (mSendLock) {
                    if (mSendKey == null) {
                        try {
                            mSendLock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        send(br, bw, mSendKey + ":" + mSendContent);
                        mSendKey = null;
                        mSendContent = null;
                        mSendLock.notifyAll();
                    }
                }
            }
        }
    }

    private void send(BufferedReader br, BufferedWriter bw, String content) throws IOException {
        if (DEBUG)
            Log.i("server", "send:" + content);
        while (!exit) {
            clear(br);
            bw.append(content + "\n");
            bw.flush();
            String resp = br.readLine();
            if (resp.equals(content)) {
                bw.append("ok\n");
                bw.flush();
                if (DEBUG)
                    Log.i("server", "send sucess:" + content);
                return;
            } else {
                if (DEBUG)
                    Log.e("server", "error response:" + resp + ", retry");
            }
        }
    }

    private boolean confirm(BufferedReader br, BufferedWriter bw, String content) throws IOException {
        clear(br);
        bw.append(content + "\n");
        bw.flush();
        String resp = br.readLine();
        if (resp.equals("ok")) {
            if (DEBUG)
                Log.i("client", "confirm sucess:" + content);
            return true;
        }
        Log.e("client", "confirm fail:" + content + " resp:" + resp);
        return false;
    }

    private void clear(BufferedReader br) throws IOException {
        while (br.ready())
            br.read();
    }

    /**
     * @param args
     * @throws UnknownHostException
     */
    public static void main(String[] args) throws UnknownHostException {
        TcpStringCommunicationOld tsc = new TcpStringCommunicationOld(InetAddress.getLocalHost()
                .getHostAddress(), true);
        final TcpStringCommunicationOld tsc1 = new TcpStringCommunicationOld(InetAddress.getLocalHost()
                .getHostAddress(), false);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!tsc1.exit){
                    try {
                        Log.e("get:" + tsc1.getContent("hello"));
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                Log.i("thd exit");
                    
            }
        }).start();
        try {
            for (int i = 0; i < 10; i++) {
                tsc.sendContent("hello", "world" + i);
                Common.sleep(100);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        tsc.dispose();
        tsc1.dispose();
        Log.i("exit");
    }
}
